Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[実装例]システムコールの実装 #14

Open
wants to merge 2 commits into
base: user_program
Choose a base branch
from
Open

Conversation

rihib
Copy link
Owner

@rihib rihib commented Aug 28, 2024

思考プロセス

システムコールを実装したい。

トラップハンドラ

  • システムコールを実現するにはUモードの際にトラップを起こし、Sモードに移行した際にシステムコールの処理を実行すれば良い。トラップを起こすにはecall命令を実行する
  • トラップハンドラでは、通常のトラップなのか、システムコールなのかを見分ける必要がある。そのためにはecall命令が呼ばれたことによってトラップが発生したかどうかをscauseの値を確認することで判別できる
  • またシステムコールの場合はトラップハンドラはシステムコールハンドラに制御を渡す

システムコールハンドラ

  • 通常、システムコールは複数存在するので、どのシステムコールが呼ばれたのかをシステムコール番号を確認して判別する必要がある。システムコール番号はecall命令に引数として渡されている。そのほかにも処理に必要な引数が渡される
  • そのため、ecall命令からの引数をシステムコールハンドラに届けるために、トラップハンドラでは汎用レジスタの値が保存されているカーネルスタックのスタックポインタの値をシステムコールハンドラに渡す必要がある

システムコール呼び出し

  • ユーザー空間側ではecall命令に引数を渡して実行し、結果の値を受け取る必要がある

テスト

void main(void) {
    printf("Hello World from shell!\n");
}

common.cにあるprintf関数はputchar関数を使用しているが、ユーザーランド上のライブラリでputcharを実装したのでそのまま使うことができる。次のようにメッセージが表示されれば成功である。

$ ./run.sh
Hello World from shell!

@rihib rihib changed the title システムコールの実装 [実装例]システムコールの実装 Aug 28, 2024
@@ -133,6 +134,7 @@ __attribute__((aligned(4))) __attribute__((naked)) void kernel_entry(void) {
"addi a0, sp, 4 * 31\n"
"csrw sscratch, a0\n"

"mv a0, sp\n" // handle_trap関数の引数としてコンテキストを保存したスタック領域のポインタを渡す
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spの値をa0にコピーする。このspの値はaddi sp, sp, -4 * 31された後の値であり、汎用レジスタの値が格納されているスタック領域の先頭アドレスである。a0にコピーしているのは、次に呼び出すhandle_trap関数の引数とするためである。スタックに保存された汎用レジスタの値はtrap_frame構造体と同じ構造になっているため、handle_trap関数にはtrap_frame構造体の型を持つ引数が渡されることになる。

uint32_t scause = READ_CSR(scause);
uint32_t stval = READ_CSR(stval);
uint32_t sepc = READ_CSR(sepc);
PANIC("unexpected trap ocurred; scause=%x, stval=%x, sepc=%x", scause, stval,
sepc);
if (scause == SCAUSE_ECALL) {
Copy link
Owner Author

@rihib rihib Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ecall 命令が呼ばれたのかどうかは、scause の値を確認することで判定できる。handle_syscall関数を呼び出す以外にも、sepcの値に4を加えている。これは、sepcはトラップを引き起こしたプログラムカウンタ、つまりecall命令を指している。変えないままだと、ecall命令を無限に繰り返し実行してしまうので、命令のサイズ分 (4バイト) だけ加算することで、ユーザーモードに戻る際に次の命令から実行を再開するようにしている。

@@ -327,3 +336,13 @@ void user_entry(void) {
WRITE_CSR(sepc, USER_BASE);
__asm__ __volatile__("sret\n");
}

void handle_syscall(struct trap_frame *f) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

トラップハンドラから呼ばれるのがシステムコールハンドラである。システムコールの種類に応じて処理を分岐する。今回は、SYS_PUTCHAR に対応する処理を実装する。単にa0レジスタに入っている文字を出力するだけである。

@@ -4,7 +4,21 @@ extern char __stack_top[];

__attribute__((noreturn)) void exit(void) { for (;;); }

void putchar(char c) { /* 後で実装する */ }
int syscall(int sysno, int arg0, int arg1, int arg2) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syscall関数は、a3にシステムコール番号、a0〜a2レジスタにシステムコールの引数を設定して ecall 命令を実行する。ecall 命令は、カーネルに処理を委譲するための特殊な命令である。ecall 命令を実行すると、トラップハンドラが呼び出され、カーネルに処理が移る。カーネルからの戻り値はa0レジスタに設定される。

@@ -4,7 +4,21 @@ extern char __stack_top[];

__attribute__((noreturn)) void exit(void) { for (;;); }

void putchar(char c) { /* 後で実装する */ }
int syscall(int sysno, int arg0, int arg1, int arg2) {
register int a0 __asm__("a0") = arg0;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変数a0をレジスタa0に対応させている。これにより変数a0を使用すると、実際にはレジスタa0を操作することになる。そのため、変数a0にarg0を代入しているので、実際はレジスタa0にarg0を格納している。

register int a3 __asm__("a3") = sysno;

__asm__ __volatile__("ecall"
: "=r"(a0)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

出力オペランドを指定する部分である。ecall命令が実行された結果をa0レジスタに格納するように指定している。


__asm__ __volatile__("ecall"
: "=r"(a0)
: "r"(a0), "r"(a1), "r"(a2), "r"(a3)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

入力オペランドを指定する部分である。rはレジスタに格納された値を使用することを意味し、a0からa3の各レジスタに格納された値がecall命令に渡される。

__asm__ __volatile__("ecall"
: "=r"(a0)
: "r"(a0), "r"(a1), "r"(a2), "r"(a3)
: "memory");
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コンパイラレベルのメモリバリアーを作り、最適化によってメモリアクセスの順序を変更しないようにする。
https://stackoverflow.com/questions/14950614/working-of-asm-volatile-memory

return a0;
}

void putchar(char ch) { syscall(SYS_PUTCHAR, ch, 0, 0); }
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

putcharシステムコールを呼び出す。このシステムコールでは、第1引数として文字を渡す。第2引数以降は、未使用なので0を渡すことにする。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant